package server

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
)

// DirEntry 是文件夹内容的信息条目
type DirEntry struct {
	ID        int64
	Name      string
	Size      int64
	IsDir     bool
	IsRegular bool
	Path      string
	MimeType  string
	IsHidden  bool
}

// 共享的文件夹路径
var rootPath string

// Cors 用于允许跨域请求
func Cors(context *gin.Context) {
	method := context.Request.Method
	// 必须，接受指定域的请求，可以使用*不加以限制，但不安全
	//context.Header("Access-Control-Allow-Origin", "*")
	context.Header("Access-Control-Allow-Origin", context.GetHeader("Origin"))
	// 必须，设置服务器支持的所有跨域请求的方法
	context.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
	// 服务器支持的所有头信息字段，不限于浏览器在"预检"中请求的字段
	context.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Token")
	// 可选，设置XMLHttpRequest的响应对象能拿到的额外字段
	context.Header("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
	// 可选，是否允许后续请求携带认证信息Cookie，该值只能是true，不需要则不设置
	context.Header("Access-Control-Allow-Credentials", "true")
	// 放行所有OPTIONS方法
	if method == "OPTIONS" {
		context.AbortWithStatus(http.StatusNoContent)
		return
	}
	context.Next()
}

// RunServer 用于启动服务
func RunServer(dir string) {

	rootPath, _ = filepath.Abs(filepath.Join(dir, "/"))
	println("您即将共享的文件夹是：", rootPath)

	gin.SetMode(gin.ReleaseMode)
	server := gin.Default()

	//view用于显示文件夹列表的前端页面
	server.Static("/view", filepath.Join(GetExeDir(), "web/"))

	//用于获取文件
	server.GET("/file", getFile)

	//用于上传文件
	if Config.AllowUpload {
		server.POST("/upload", uploadFile)
	}

	//directory用于获取文件夹内容信息
	server.GET("/list", getList)

	//下载打包好的文件夹
	if Config.AllowGetFolder {
		server.GET("/folder", getFolder)
	}

	//config
	server.GET("/config", getConfig)

	//获取md5
	server.GET("/md5", getMd5)

	PrintInterfaces(InternalConfig.Port)

	err := server.Run(InternalConfig.Host + ":" + strconv.Itoa(InternalConfig.Port))
	if err != nil {
		println(err.Error())
	}
}
func getFile(context *gin.Context) {
	Cors(context)
	filename := filepath.Join(rootPath, context.Query("path"))

	if !IsSubDir(rootPath, filename) {
		PrintWarring(context.Request.Host + " " + ErrMessOutDir)
		requestError(context, errors.New(RequestAbnormalAccess))
		return
	}

	println(filename)
	if !IsExist(filename) {
		requestError(context, errors.New(RequestNotFound+filename))
		return
	}

	context.Header("Content-Type", "application/octet-stream")
	context.Header("Content-Disposition", "attachment; filename="+filepath.Base(filename))
	context.Header("Content-Transfer-Encoding", "binary")
	context.File(filename)
}

func getMd5(context *gin.Context) {
	Cors(context)
	filename := filepath.Join(rootPath, context.Query("path"))

	if !IsSubDir(rootPath, filename) {
		PrintWarring(context.Request.Host + " " + ErrMessOutDir)
		requestError(context, errors.New(RequestAbnormalAccess))
		return
	}

	file, err := os.Open(filename)
	if err != nil {
		requestError(context, err)
		return
	}
	defer file.Close()

	hash := md5.New()
	if _, err = io.Copy(hash, file); err != nil {
		requestError(context, err)
		return
	}
	hashInBytes := hash.Sum(nil)[:16]
	context.String(200, hex.EncodeToString(hashInBytes))
	return

}

func getConfig(context *gin.Context) {
	Cors(context)
	context.JSON(200, Config)
}

// getFolder 用于下载文件夹
func getFolder(context *gin.Context) {
	Cors(context)

	path := context.Query("path")
	dir := filepath.Join(rootPath, path)

	if !IsSubDir(rootPath, dir) {
		PrintWarring(context.Request.Host + " " + ErrMessOutDir)
		requestError(context, errors.New(RequestAbnormalAccess))
		return
	}

	if !IsExist(dir) {
		requestError(context, errors.New(RequestNotFound+path))
		return
	}
	if !IsDir(dir) {
		requestError(context, errors.New(path+RequestNotADirectory))
		return
	}

	tempFile, err := os.CreateTemp(os.TempDir(), filepath.Base(dir))
	if err != nil {
		return
	}

	err = buildZip(dir, tempFile.Name())
	if err != nil {
		requestError(context, err)
		return
	}
	filename := filepath.Base(tempFile.Name()) + ".zip"
	if filename[:1] == "." {
		filename = filename[1:]
	}
	context.Header("Content-Type", "application/octet-stream")
	context.Header("Content-Disposition", "attachment; filename="+filename)
	context.Header("Content-Transfer-Encoding", "binary")
	context.File(tempFile.Name())
}

// uploadFile 用于接受上传的文件
func uploadFile(context *gin.Context) {

	Cors(context)
	dir := filepath.Join(rootPath, context.Query("path"))

	from, err := context.MultipartForm()
	if err != nil {
		requestError(context, err)
		return
	}
	files := from.File["upload"]
	for _, file := range files {
		filename := filepath.Join(dir, file.Filename)

		_, err := os.Stat(filename)

		if err == nil {
			// 文件存在
			requestError(context, errors.New(filename+RequestIsExist))
			return
		}

		err = context.SaveUploadedFile(file, filepath.Join(dir, file.Filename))
		if err != nil {
			requestError(context, err)
			return
		}
	}
	context.JSON(200, gin.H{
		"message": RequestSuccess,
	})
}

// getList 是获取文件夹内容的api接口
func getList(context *gin.Context) {
	Cors(context)
	path := context.Query("path")
	println(path)
	abs, err := filepath.Abs(filepath.Join(rootPath, path))
	if err != nil {
		requestError(context, err)
		return
	}
	if !IsSubDir(rootPath, abs) {
		PrintWarring(context.Request.Host + " " + ErrMessOutDir)
		requestError(context, errors.New(RequestAbnormalAccess))
		return
	}
	entryList, err := os.ReadDir(filepath.Join(rootPath, path))
	if err != nil {
		requestError(context, err)
		return
	}

	var list []DirEntry //这是将要返回的结果
	var num int64
	num = 0
	for _, entry := range entryList {

		//获取基本信息
		info, err := entry.Info()
		if err != nil {
			requestError(context, err)
			return
		}

		//排除软链接
		if info.Mode().Type() == os.ModeSymlink {
			continue
		}

		mime := ""
		if !info.IsDir() {
			if info.Size() == 0 {
				continue
			}
			//获取MimeType信息
			mime = GetFileContentType(filepath.Join(rootPath, path, entry.Name()))
			if err != nil {
				requestError(context, err)
				return
			}
		}
		fileInfo, err := entry.Info()
		if err != nil {
			requestError(context, err)
			return
		}
		relPath, err := filepath.Rel(rootPath, filepath.Join(abs, entry.Name()))
		if runtime.GOOS == "windows" {
			relPath = strings.ReplaceAll(relPath, "\\", "/")
		}
		if err != nil {
			requestError(context, err)
			return
		}
		isHidden, err := IsHiddenFile(filepath.Join(abs, entry.Name()))
		if err != nil {
			requestError(context, err)
			return
		}
		list = append(list, DirEntry{
			ID:        num,
			Name:      entry.Name(),
			Size:      info.Size(),
			IsDir:     entry.IsDir(),
			IsRegular: fileInfo.Mode().IsRegular(),
			Path:      relPath,
			MimeType:  mime,
			IsHidden:  isHidden,
		})
		num++

	}
	context.JSON(http.StatusOK, list)
}

// requestError 用于向请求返回一个错误
func requestError(context *gin.Context, err error) {
	context.JSON(http.StatusBadRequest, gin.H{
		"Error": err.Error(),
	})
}
